package fever.utils 
{
	import fever.structures.colors.HSL;
	import fever.structures.colors.HSLRange;
	import fever.structures.colors.HSL_WINDOWS_RANGE;
	import fever.structures.colors.HSV;
	import fever.structures.colors.HSVRange;
	import fever.structures.colors.HSV_PHOTOSHOP_RANGE;
	import fever.structures.colors.RGB;	

	/**
	 * Colors utilities.
	 * 
	 * @author Romain Ecarnot
	 */
	final public class ColorSpace 
	{
		//--------------------------------------------------------------------
		// Constants
		//--------------------------------------------------------------------
			
		/** 
		 * HSV range used in application.
		 * 
		 * @default fever.structures.colors.HSVRange#PHOTOSHOP
		 * 
		 * @see fever.structures.colors.HSVRange		 * @see fever.structures.colors.HSVRange#PHOTOSHOP
		 */
		public static var HSVRANGE : HSVRange = HSV_PHOTOSHOP_RANGE;
		
		/** 
		 * HSL range used in application.
		 * 
		 * @default fever.structures.colors.HSLRange#WINDOWS
		 * 
		 * @see fever.structures.colors.HSLRange		 * @see fever.structures.colors.HSLRange#WINDOWS
		 */
		public static var HSLRANGE : HSLRange = HSL_WINDOWS_RANGE;
		
		
		//-------------------------------------------------------------------------
		// Public API
		//-------------------------------------------------------------------------
		
		/**
		 * Transforms the passed-in <code>rgb</code> instance to 
		 * <code>HSL</code> structure.
		 * 
		 * @param rgb RGB instance
		 * 
		 * @see fever.structures.colors.HSL
		 * @see fever.structures.colors.RGB
		 */
		public static function rgbToHsl( rgb : RGB ) : HSL
		{
			var r : Number = ( rgb.red / 255 );
			var g : Number = ( rgb.green / 255 );
			var b : Number = ( rgb.blue / 255 );
			
			var min : Number = Math.min( Math.min( r, g ), b );
			var max : Number = Math.max( Math.max( r, g ), b );
			var diff : Number = max - min;
			
			var hue : Number;
			var sat : Number;
			var lum : Number = ( max + min ) / 2;
			
			if ( diff == 0 )
			{
				hue = 0;
				sat = 0;
			}
			else
			{
				if ( lum < 0.5 ) sat = diff / ( max + min );
	  			 else  sat = diff / ( 2 - max - min );
				
				var dr : Number = ( ( ( max - r ) / 6 ) + ( diff / 2 ) ) / diff;
				var dg : Number = ( ( ( max - g ) / 6 ) + ( diff / 2 ) ) / diff;
				var db : Number = ( ( ( max - b ) / 6 ) + ( diff / 2 ) ) / diff;
				
				if ( r == max ) hue = db - dg;
				else if ( g == max ) hue = ( 1 / 3 ) + dr - db;
				else if ( b == max ) hue = ( 2 / 3 ) + dg - dr;
				
				if ( hue < 0 ) ; 
				hue += 1;
				if ( hue > 1 ) ; 
				hue -= 1;
			}
			
			return new HSL( 
				Math.round( hue * HSLRANGE.hue.max ), 
				Math.round( sat * HSLRANGE.saturation.max ), 
				Math.round( lum * HSLRANGE.luminance.max ) 
			);
		}

		/**
		 * Transforms the passed-in <code>rgb</code> instance to 
		 * <code>HSV</code> structure.
		 * 
		 * @param rgb RGB instance
		 * 
		 * @see fever.structures.colors.HSV
		 * @see fever.structures.colors.RGB
		 */
		public static function rgbToHsv( rgb : RGB ) : HSV
		{
			var r : Number = ( rgb.red / 255 );
			var g : Number = ( rgb.green / 255 );
			var b : Number = ( rgb.blue / 255 );
			
			var min : Number = Math.min( Math.min( r, g ), b );
			var max : Number = Math.max( Math.max( r, g ), b );
			var del_Max : Number = max - min;
			
			var hue : Number;
			var sat : Number;
			var value : Number = max;
			
			if ( del_Max == 0 )
			{
				hue = 0;
				sat = 0;
			}
			else
			{
				sat = del_Max / max;
				
				var dr : Number = ( ( ( max - r ) / 6 ) + ( del_Max / 2 ) ) / del_Max;
				var dg : Number = ( ( ( max - g ) / 6 ) + ( del_Max / 2 ) ) / del_Max;
				var db : Number = ( ( ( max - b ) / 6 ) + ( del_Max / 2 ) ) / del_Max;
				
				if ( r == max ) hue = db - dg;
				else if ( g == max ) hue = ( 1 / 3 ) + dr - db;
				else if ( b == max ) hue = ( 2 / 3 ) + dg - dr;
				
				if ( hue < 0 ) ; 
				hue += 1;
				if ( hue > 1 ) ; 
				hue -= 1;
			}
			
			return new HSV( 
				Math.round( hue * HSVRANGE.hue.max ), 
				Math.round( sat * HSVRANGE.saturation.max ), 
				Math.round( value * HSVRANGE.value.max ) 
			);
		}

		/**
		 * Transforms the passed-in <code>rgb</code> instance to 
		 * hexa value.
		 * 
		 * @param rgb RGB instance
		 * 
		 * @see fever.structures.colors.RGB
		 */
		public static function rgbToHexa( rgb : RGB ) : uint
		{
			return ( (rgb.red << 16) | (rgb.green << 8) | rgb.blue );	
		}

		/**
		 * Transforms the passed-in <code>rgb</code> instance to 
		 * string value.
		 * 
		 * @param rgb RGB instance
		 * 
		 * @see fever.structures.colors.RGB
		 */
		public static function rgbToString( rgb : RGB ) : String
		{
			return hexaToString( rgbToHexa( rgb ) );
		}

		/**
		 * Transforms the passed-in <code>hsl</code> instance to 
		 * <code>RGB</code> structure.
		 * 
		 * @param hsl HSL instance
		 * 
		 * @see fever.structures.colors.HSL
		 * @see fever.structures.colors.RGB
		 */
		public static function hslToRgb( hsl : HSL ) : RGB
		{
			var r : Number;
			var g : Number;
			var b : Number;
			
			var hue : Number = hsl.hue / HSLRANGE.hue.max;
			var sat : Number = hsl.saturation / HSLRANGE.saturation.max;
			var lum : Number = hsl.luminance / HSLRANGE.luminance.max;
			
			if ( sat == 0 )
			{
				r = lum * 255;
				g = lum * 255;
				b = lum * 255;
			}
			else
			{
				var tmp : Number;
				if ( lum < 0.5 ) tmp = lum * ( 1 + sat );
				else tmp = ( lum + sat ) - ( sat * lum );
			
				var diff : Number = 2 * lum - tmp;
			
				r = 255 * _hueToRgb( diff, tmp, hue + ( 1 / 3 ) );
				g = 255 * _hueToRgb( diff, tmp, hue );
				b = 255 * _hueToRgb( diff, tmp, hue - ( 1 / 3 ) );
			}
			
			return new RGB( 
				Math.round( r ), 
				Math.round( g ), 
				Math.round( b ) 
			);
		}

		/**
		 * Transforms the passed-in <code>hsl</code> instance to 
		 * <code>HSV</code> structure.
		 * 
		 * @param hsl HSL instance
		 * 
		 * @see fever.structures.colors.HSL
		 * @see fever.structures.colors.HSV
		 */
		public static function hslToHsv( hsl : HSL ) : HSV
		{
			return rgbToHsv( hslToRgb( hsl ) );	
		}

		/**
		 * Transforms the passed-in <code>hsl</code> instance to 
		 * hexa value.
		 * 
		 * @param hsl HSL instance
		 * 
		 * @see fever.structures.colors.HSL
		 */
		public static function hslToHexa( hsl : HSL ) : Number
		{
			return rgbToHexa( hslToRgb( hsl ) );
		}

		/**
		 * Transforms the passed-in <code>hsl</code> instance to 
		 * string value.
		 * 
		 * @param hsl HSL instance
		 * 
		 * @see fever.structures.colors.HSL
		 */
		public static function hslToString( hsl : HSL ) : String
		{
			return rgbToString( hslToRgb( hsl ) );
		}

		/**
		 * Transforms the passed-in <code>hsv</code> instance to 
		 * <code>RGB</code> structure.
		 * 
		 * @param hsv HSV instance
		 * 
		 * @see fever.structures.colors.HSV
		 * @see fever.structures.colors.RGB
		 */
		public static function hsvToRgb( hsv : HSV ) : RGB
		{
			var r : Number;
			var g : Number;
			var b : Number;
			
			var hue : Number = hsv.hue / HSVRANGE.hue.max;
			var sat : Number = hsv.saturation / HSVRANGE.saturation.max;
			var value : Number = hsv.value / HSVRANGE.value.max;
			
			if ( sat == 0 )
			{
				r = value * 255;
				g = value * 255;
				b = value * 255;
			}
			else
			{
				var nr : Number;
				var ng : Number;
				var nb : Number;
				var tmpHue : Number = hue * 6;
				
				if ( tmpHue == 6 ) tmpHue = 0;
				
				var i : Number = int( tmpHue );
				var v1 : Number = value * ( 1 - sat );
				var v2 : Number = value * ( 1 - sat * ( tmpHue - i ) );
				var v3 : Number = value * ( 1 - sat * ( 1 - ( tmpHue - i ) ) );
				
				if ( i == 0 )
				{
					nr = value;
					ng = v3;
					nb = v1;
				}
				else if ( i == 1 )
				{
					nr = v2;
					ng = value;
					nb = v1;
				}
				else if ( i == 2 )
				{
					nr = v1;
					ng = value;
					nb = v3;
				}
				else if ( i == 3 )
				{
					nr = v1;
					ng = v2;
					nb = value;
				}
				else if ( i == 4 )
				{
					nr = v3;
					ng = v1;
					nb = value;
				}
				else
				{
					nr = value;
					ng = v1;
					nb = v2;
				}
			
				r = nr * 255;
				g = ng * 255;
				b = nb * 255;
			}
	
			return new RGB( 
				Math.round( r ), 
				Math.round( g ), 
				Math.round( b ) 
			);
		}

		/**
		 * Transforms the passed-in <code>hsv</code> instance to 
		 * <code>HSL</code> structure.
		 * 
		 * @param hsv HSV instance
		 * 
		 * @see fever.structures.colors.HSV
		 * @see fever.structures.colors.HSL
		 */
		public static function hsvToHsl( hsv : HSV ) : HSL
		{
			return rgbToHsl( hsvToRgb( hsv ) );
		}

		/**
		 * Transforms the passed-in <code>hsv</code> instance to 
		 * hexa value.
		 * 
		 * @param hsv HSV instance
		 * 
		 * @see fever.structures.colors.HSV
		 */
		public static function hsvToHexa( hsv : HSV ) : Number
		{
			return rgbToHexa( hsvToRgb( hsv ) );
		}

		/**
		 * Transforms the passed-in <code>hsv</code> instance to 
		 * string value.
		 * 
		 * @param hsv HSV instance
		 * 
		 * @see fever.structures.colors.HSV
		 */
		public static function hsvToString( hsv : HSV ) : String
		{
			return rgbToString( hsvToRgb( hsv ) );
		}

		/**
		 * Transforms the passed-in <code>hexa</code> value to 
		 * <code>RGB</code> structure.
		 * 
		 * @param hexa int
		 * 
		 * @see fever.structures.colors.RGB
		 */
		public static function hexaToRgb( hexa : int ) : RGB
		{
			var r : int = hexa >> 16;
			var gb : int = hexa ^ r << 16;
			var g : int = gb >> 8;
			var b : int = gb ^ g << 8;
			
			return new RGB( r, g, b );
		}

		/**
		 * Transforms the passed-in <code>hexa</code> value to 
		 * <code>HSL</code> structure.
		 * 
		 * @param hexa int
		 * 
		 * @see fever.structures.colors.HSL
		 */
		public static function hexaToHsl( hexa : int ) : HSL
		{
			return rgbToHsl( hexaToRgb( hexa ) );	
		}

		/**
		 * Transforms the passed-in <code>hexa</code> value to 
		 * <code>HSV</code> structure.
		 * 
		 * @param hexa int
		 * 
		 * @see fever.structures.colors.HSV
		 */
		public static function hexaToHsv( hexa : int ) : HSV
		{
			return rgbToHsv( hexaToRgb( hexa ) );	
		}

		/**
		 * Transforms the passed-in <code>hexa</code> value to 
		 * string value.
		 * 
		 * @param hexa int
		 */
		public static function hexaToString( hexa : Number ) : String
		{
			var s : String = hexa.toString( 16 );
			var l : Number = 6 - s.length;
			while (l--) s = "0" + s;
			return s.toUpperCase( );	
		}

		/**
		 * Transforms the passed-in string to hexa number.
		 */
		public static function stringToHexa( str : String ) : int
		{
			str = str.substr( -6, 6 );
			return parseInt( str, 16 ) as int;
		}

		/**
		 * Transforms the passed-in <code>string</code> value to 
		 * <code>RGB</code> structure.
		 * 
		 * @param str Hexa string value
		 * 
		 * @see fever.structures.colors.RGB
		 */
		public static function stringToRgb( str : String ) : RGB
		{
			return hexaToRgb( stringToHexa( str ) );
		}

		/**
		 * Transforms the passed-in <code>string</code> value to 
		 * <code>HSL</code> structure.
		 * 
		 * @param str Hexa string value
		 * 
		 * @see fever.structures.colors.HSL
		 */
		public static function stringToHsl( str : String ) : HSL
		{
			return hexaToHsl( stringToHexa( str ) );
		}

		/**
		 * Transforms the passed-in <code>string</code> value to 
		 * <code>HSV</code> structure.
		 * 
		 * @param str Hexa string value
		 * 
		 * @see fever.structures.colors.HSV
		 */
		public static function stringToHsv( str : String ) : HSV
		{
			return hexaToHsv( stringToHexa( str ) );
		}
		
		/**
		 * Performs a linear brightness adjustment of an RGB color.
		 * 
		 * @param rgb Original RGB color.
	 	 * @param value Amount to be added to each color channel ( -255 to 255 ).
	 	 * 
	 	 * @return New RGB Color
		 */
		public static function adjustLinearBrightness( color : uint, value : int = 0 ) : uint
		{
			if ( value == 0 ) return color;
			
			var r : Number = Math.max( Math.min( ( ( color >> 16 ) & 0xFF) + value, 255 ), 0 );
			var g : Number = Math.max( Math.min( ( ( color >> 8 ) & 0xFF) + value, 255 ), 0 );
			var b : Number = Math.max( Math.min( ( color & 0xFF ) + value, 255 ), 0 );
			
			return ( r << 16 ) | ( g << 8 ) | b;
		}
		
		/**
	 	 * Performs a scaled brightness adjustment of an RGB color.
	 	 * 
	 	 * @param rgb Original RGB color.
	 	 * @param value The percentage to brighten or darken the 
	 	 * original color. ( -100 to 100 )
	 	 */ 
		public static function adjustScaleBrightness( color : uint, value : Number = 0 ) : uint
		{
			var r : Number;
			var g : Number;
			var b : Number;
			
			if ( value == 0 ) return color;
			
			if ( value < 0 )
			{
				value = ( 100 + value ) / 100;
				r = ( ( color >> 16 ) & 0xFF ) * value;
				g = ( ( color >> 8 ) & 0xFF ) * value;
				b = ( color & 0xFF ) * value;
			}
			else
			{
				value /= 100;
				r = ( (color >> 16 ) & 0xFF );
				g = ( (color >> 8 ) & 0xFF );
				b = ( color & 0xFF );
				
				r += ( ( 0xFF - r ) * value );
				g += ( ( 0xFF - g ) * value );
				b += ( ( 0xFF - b ) * value );
				
				r = Math.min( r, 255 );
				g = Math.min( g, 255 );
				b = Math.min( b, 255 );
			}
			
			return ( r << 16 ) | ( g << 8 ) | b;
		}
		
		/**
		 * Performs an RGB multiplication of two RGB colors.
		 *
		 * @param color1 First RGB color.
		 * @param color2 Second RGB color.
		 *
		 * @return RGB multiplication of the two colors.
		 */
		public static function rgbMultiply( color1 : uint, color2 : uint ) : uint
		{
			var r1 : Number = ( color1 >> 16 ) & 0xFF;
			var g1 : Number = ( color1 >> 8 ) & 0xFF;
			var b1 : Number = color1 & 0xFF;
			
			var r2 : Number = ( color2 >> 16 ) & 0xFF;
			var g2 : Number = ( color2 >> 8 ) & 0xFF;
			var b2 : Number = color2 & 0xFF;
			
			return ( ( r1 * r2 / 255 ) << 16 ) | ( ( g1 * g2 / 255 ) << 8 ) | ( b1 * b2 / 255 );
		} 
	
		
		//--------------------------------------------------------------------
		// Private implementations
		//--------------------------------------------------------------------
						
		/** @private */
		public function ColorSpace( access : PrivateConstructorAccess ) 
		{
		}
		
		private static function _hueToRgb( diff : Number, sat : Number, hue : Number ) : Number
		{
			if ( hue < 0 ) hue += 1;
			if ( hue > 1 ) hue -= 1;
			if ( ( 6 * hue ) < 1 ) return ( diff + ( sat - diff ) * 6 * hue );
			if ( ( 2 * hue ) < 1 ) return ( sat );
			if ( ( 3 * hue ) < 2 ) return ( diff + ( sat - diff ) * ( ( 2 / 3 ) - hue ) * 6 );
			return ( diff );
		}
	}
}


internal class PrivateConstructorAccess 
{
}